/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  Author: g0tsu
 *  Email:  g0tsu at dnmx.0rg
 */

#include <stdio.h>
#include <zip.h>
#include <zipfs.h>
#include <stdlib.h>
#include <string.h>

#define ZIPFS_DEFAULT_DIRSIZE 4096

char *zipfs_get_entry_name(const char *str, const int len) {
  int i;
  int name_size;
  char *out = NULL;
  char *path = strdup(str);

  for (i = len; i > 0; i--)
    if (path[i] == '/' && path[i - 1] != '\\')
      break;

  name_size = len - i;
  out = malloc(name_size * sizeof(char) + 1);

  if (*(path + i) == '/')
    i++;

  memcpy(out, path + i, name_size);
  out[name_size] = 0;
  free(path);
  return out;
}

char *zipfs_get_entry_parent(const char *str, const int len) {
  int i = len;
  char *out = NULL;
  char *path = strdup(str);

  memcpy(path, str, len);

  if (len > 1 && path[len - 1] == '/' && path[len - 2] != '\\') {
    path[len - 1] = '\0';
  }

  for (i = len; i > 0; i--)
    if (path[i] == '/' && path[i - 1] != '\\')
      break;

  out = malloc(i * sizeof(char) + 1);
  memcpy(out, path, i);
  out[i] = 0;
  free(path);
  return out;
}

static int zipfs_get_entry_parent_index(const char *str, const int len) {
  for (int i = 0; i < zipfs.entries_size; i++) {
    if (zipfs.entries[i]->isdir == 0)
      continue;

    if (strncmp(str, zipfs.entries[i]->orig, len) == 0) {
      return i;
    }
  }

  return -1;
}

static void zipfs_fill_root_entries(void) {
  char *rname = malloc(BUFSIZ);
  char *cp;
  int rs;
  uint8_t skip, isdir;

  for (int i = 0; i < zipfs.entries_size; i++) {
    cp = zipfs.entries[i]->orig;
    skip = 0;
    isdir = 0;

    for (rs = 0; rs < zipfs.entries[i]->orig_len; rs++) {
      cp++;
      if ((*cp == '/' || *cp == '\0') && rs > 0) {
        rs++;
        if (*cp == '/')
          isdir = 1;
        break;
      }
    }

    if (rs < 1)
      continue;

    memcpy(rname, zipfs.entries[i]->orig, rs);
    rname[rs] = 0;

    for (int j = 0; j < zipfs.entries_size; j++) {
      if (zipfs.entries[j]->parent[0] == '/' &&
          strcmp(zipfs.entries[j]->name, rname) == 0) {
        skip = 1;
        break;
      }
    }

    if (skip == 1)
      continue;

    struct zentry *item = malloc(sizeof(struct zentry));
    item->orig = strdup(rname);
    item->orig_len = rs;
    item->name = strdup(rname);
    item->name_len = rs;
    item->parent = strdup("/");
    item->parent_len = 1;
    item->isdir = isdir;

    if (isdir)
      item->size = ZIPFS_DEFAULT_DIRSIZE;
    else
      item->size = 0;

    zipfs.entries = realloc(zipfs.entries,
        (zipfs.entries_size + 1) * sizeof(struct zentry));
    zipfs.entries[zipfs.entries_size++] = item;

/*    printf("%s<\t>%s\n", zipfs.entries[i]->parent, zipfs.entries[i]->name);*/

  }

  for (int i = 0; i < zipfs.entries_size; i++) {
    cp = zipfs.entries[i]->parent;

    if (*cp && *cp != '/' &&
        zipfs_get_entry_parent_index(zipfs.entries[i]->parent,
        zipfs.entries[i]->parent_len) < 0) {

      struct zentry *item = malloc(sizeof(struct zentry));
      item->orig = strdup(zipfs.entries[i]->parent);
      item->orig_len = zipfs.entries[i]->parent_len;
      item->name = zipfs_get_entry_name(item->orig, item->orig_len);
      item->name_len = item->orig_len;
      item->parent = zipfs_get_entry_parent(item->orig, item->orig_len);
      item->parent_len = strlen(item->parent);;
      item->isdir = 1;
      item->size = ZIPFS_DEFAULT_DIRSIZE;

      zipfs.entries = realloc(zipfs.entries,
          (zipfs.entries_size + 1) * sizeof(struct zentry));
      zipfs.entries[zipfs.entries_size++] = item;

/*      printf(">> Noparent for %s, %s %s <%s>\n",*/
/*          cp, zipfs.entries[i]->parent, item->name,*/
/*          item->parent);*/
    }
  }

  free(rname);
}

struct zint_arr *zipfs_find_entries(const char *str, const int len) {
  char *parent = strdup(str);
  int ps = len;
  struct zint_arr *out = malloc(sizeof(struct zint_arr));
  out->size = 0;
  out->arr = malloc(sizeof(int));

  if (parent[0] == '/' && len > 1) {
    memcpy(parent, str+1, ps--);
    parent[ps] = 0;
  }

  if (parent[ps - 1] == '/') {
    parent[ps - 1] = 0;
    ps--;
  }

  if (ps == 0) {
    parent[0] = '/';
    parent[1] = 0;
    ps = 1;
  }

/*  printf("search for parent: '%s'\n", parent);*/
  for (int i = 0; i < zipfs.entries_size; i++) {
    if (zipfs.entries[i]->parent_len == ps &&
        strcmp(parent, zipfs.entries[i]->parent) == 0) {
      out->arr = realloc(out->arr, (out->size + 1) * sizeof(int));
      out->arr[out->size] = malloc(sizeof(int));
      *out->arr[out->size++] = i;
/*      printf("found idx: %d %s\n", i, zipfs.entries[i]->name);*/
    }
  }

  free(parent);
  return out;
}

void zipfs_free_zint_arr(struct zint_arr *o) {
  free(o->arr);
  free(o);
}

void zipfs_fill_entries(void) {
  int n;
  struct zip_t *zip;

  if (zipfs.entries)
    return;

   zip = zip_open(zipfs.zipfile, 0, 'r');

  if (!zip) {
    fprintf(stderr, "Unable to open: %s\n", zipfs.zipfile);
    return;
  }

  n = zip_entries_total(zip);

  if (n < 1) {
    return;
  }

  zipfs.entries_size = n;
  zipfs.entries = malloc(n * sizeof(struct zentry));

  for (int i = 0; i < n; i++) {
    zip_entry_openbyindex(zip, i);
    {
      char *origname = strdup(zip_entry_name(zip));
      int orig_len = strlen(origname);
      struct zentry *item = malloc(sizeof(struct zentry));

      if (origname[orig_len - 1] == '/') {
        origname[--orig_len] = 0;
      }

      item->orig = (char *)origname;
      item->orig_len = orig_len;
      item->name = zipfs_get_entry_name(origname, orig_len);
      item->name_len = strlen(item->name);
      item->parent = zipfs_get_entry_parent(origname, orig_len);
      item->parent_len = strlen(item->parent);
      item->isdir = zip_entry_isdir(zip);
      if (item->isdir)
        item->size = ZIPFS_DEFAULT_DIRSIZE;
      else
        item->size = zip_entry_size(zip);

      zipfs.entries[i] = item;
/*      printf("[%s] %s<\t>%s\n", zipfs.entries[i]->orig,*/
/*          zipfs.entries[i]->parent, zipfs.entries[i]->name);*/
    }
    zip_entry_close(zip);
  }

  zip_close(zip);
  zipfs_fill_root_entries();
}

void zipfs_free_entries(void) {
  if (!zipfs.entries)
    return;

  for (int i = 0; i < zipfs.entries_size; i++) {
    free(zipfs.entries[i]->name);
    free(zipfs.entries[i]->parent);
    free(zipfs.entries[i]->orig);
    free(zipfs.entries[i]);
  }

  free(zipfs.entries);
  zipfs.entries = NULL;
  zipfs.entries_size = 0;
}

